package org.etnaframework.module.jdbc;

import java.util.*;
import java.util.function.Function;

/**
 * 数据库查询sql生成器
 *
 * @author jasyaf
 * @since 2023-12-27
 */
public class SqlBuilder {

    private static final String[] formats;

    static {
        formats = new String[20];
        formats[0] = null; // 特此提醒，不要传长度为0的列表！
        formats[1] = "(?)";
        for (int i = 2; i < formats.length; i++) {
            StringBuilder tmp = new StringBuilder("(");
            for (int j = 1; j < i; j++) {
                tmp.append("?, ");
            }
            tmp.append("?)");
            formats[i] = tmp.toString();
        }
    }

    StringBuilder sql;

    List<Object> args;

    /**
     * 标记是否自动添加过where
     */
    boolean addWhere = false;

    /**
     * sql的保留字，使用时必须圈起来，否则有可能会解析出错，此处可根据不同的sql方言设置不同的圈起方式
     */
    private final String quoteString;

    SqlBuilder(String quoteString) {
        this.sql = new StringBuilder();
        this.args = new ArrayList<>();
        this.quoteString = quoteString;
    }

    public StringBuilder getSql() {
        return sql;
    }

    public List<Object> getArgs() {
        return args;
    }

    /**
     * 添加sql语句片段和参数
     */
    public SqlBuilder add(String sqlPart, Object... objs) {
        if (sql.length() > 1 && sql.charAt(sql.length() - 1) != ' ') {
            sql.append(' ');
        }
        sql.append(sqlPart);
        Collections.addAll(this.args, objs);
        return this;
    }

    /**
     * 添加sql语句片段和参数
     */
    public SqlBuilder add(String sqlPart, List<Object> objs) {
        if (sql.charAt(sql.length() - 1) != ' ') {
            sql.append(' ');
        }
        sql.append(sqlPart);
        Collections.addAll(this.args, objs.toArray());
        return this;
    }

    /**
     * 添加sql语句片段和参数
     */
    public SqlBuilder add(SqlBuilder sqlBuilder) {
        if (sql.charAt(sql.length() - 1) != ' ') {
            sql.append(' ');
        }
        sql.append(sqlBuilder.sql);
        Collections.addAll(this.args, sqlBuilder.args.toArray());
        return this;
    }

    /**
     * 添加sql语句参数
     */
    public SqlBuilder addArg(Object arg) {
        args.add(arg);
        return this;
    }

    /**
     * <pre>
     * 本方法用于生成多条件查询的sql，自动添加where/and等条件，减少相关判断的代码量
     *
     * 条件个数不会完全确定，要根据前端是否传入对应的条件才能确定是否参与生成sql语句
     * 如果之前没有通过本方法添加where就会自动添加，参考代码如下：
     *
     * SqlBuilder sql = new SqlBuilder("select * from test_table");
     * if (null != name) {
     *     sql.addWhereAnd("name like ?", "'%" + StringTools.escapeSqlLikePattern(name) + "%'");
     * }
     * if (null != minAge) {
     *     sql.addWhereAnd("age>?", minAge);
     * }
     * if (null != maxAge) {
     *     sql.addWhereAnd("age<=?", maxAge);
     * }
     * if (CollectionTools.isNotEmpty(list)) {
     *     sql.addWhereOr("current_status in").addInArgs(list);
     * }
     * </pre>
     */
    public SqlBuilder addWhereAnd(String sqlPart, Object... objs) {
        if (addWhere) {
            add("and").add(sqlPart, objs);
        } else {
            add("where").add(sqlPart, objs);
            addWhere = true;
        }
        return this;
    }

    /**
     * <pre>
     * 本方法用于生成多条件查询的sql，自动添加where/or等条件，减少相关判断的代码量
     *
     * 条件个数不会完全确定，要根据前端是否传入对应的条件才能确定是否参与生成sql语句
     * 如果之前没有通过本方法添加where就会自动添加，参考代码如下：
     *
     * SqlBuilder sql = new SqlBuilder("select * from test_table");
     * if (null != name) {
     *     sql.addWhereAnd("name like ?", "'%" + StringTools.escapeSqlLikePattern(name) + "%'");
     * }
     * if (null != minAge) {
     *     sql.addWhereAnd("age>?", minAge);
     * }
     * if (null != maxAge) {
     *     sql.addWhereAnd("age<=?", maxAge);
     * }
     * if (CollectionTools.isNotEmpty(list)) {
     *     sql.addWhereOr("current_status in").addInArgs(list);
     * }
     * </pre>
     */
    public SqlBuilder addWhereOr(String sqlPart, Object... objs) {
        if (addWhere) {
            add("or").add(sqlPart, objs);
        } else {
            add("where").add(sqlPart, objs);
            addWhere = true;
        }
        return this;
    }

    /**
     * 本方法用于生成多条件查询的sql，自动添加where/and条件，减少相关判断的代码量
     * 字段为空则不加入条件
     * addWhereAndIf("age>?", minAge)
     */
    public SqlBuilder addWhereAndIf(String sqlPart, Object obj) {
        if (obj == null) {
            return this;
        }
        if (obj instanceof String) {
            if (((CharSequence) obj).length() == 0) {
                return this;
            }
        }
        addWhereAnd(sqlPart, obj);
        return this;
    }

    /**
     * 生成指定数量的?符号，用,分隔（带缓存）
     *
     * @param argsLen 生成的数量
     */
    String getMarks(int argsLen) {
        if (argsLen == 0) {
            throw new IllegalArgumentException("argsLen must >0");
        }
        if (formats.length > argsLen) {
            return formats[argsLen];
        }
        // 超过一定数量就动态生成，不再缓存
        StringBuilder tmp = new StringBuilder("(");
        for (int i = 1; i < argsLen; i++) {
            tmp.append("?, ");
        }
        tmp.append("?)");
        return tmp.toString();
    }

    /**
     * 禁止null传入，否则sql生成肯定不正确，到了jdbc层抛出的异常很难看出问题来
     */
    private void _checkNull(Object obj) {
        if (null == obj) {
            throw new NullPointerException();
        }
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(int[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(long[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(char[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(boolean[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(byte[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(float[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(double[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(short[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public SqlBuilder addInArgs(Object[] list) {
        _checkNull(list);
        add(getMarks(list.length));
        args.addAll(Arrays.asList(list));
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public <T> SqlBuilder addInArgs(Collection<T> list) {
        _checkNull(list);
        add(getMarks(list.size()));
        for (Object obj : list) {
            args.add(obj);
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public <T, R> SqlBuilder addInArgs(T[] list, Function<T, R> function) {
        _checkNull(list);
        add(getMarks(list.length));
        for (T t : list) {
            args.add(function.apply(t));
        }
        return this;
    }

    /**
     * 添加展开XX in (...) 中的列表参数的sql，前缀sql写成where xxx in即可，程序展开时会自动加()
     *
     * @param list 待展开的参数列表
     */
    public <T, R> SqlBuilder addInArgs(Collection<T> list, Function<T, R> function) {
        _checkNull(list);
        add(getMarks(list.size()));
        for (T t : list) {
            args.add(function.apply(t));
        }
        return this;
    }

    /**
     * 使用传入的class的字段生成 select 字段名 的sql
     *
     * @param tableName 表名，用于添加类似 t.id 的sql，如果传入null或空字符串就不生成表名字段
     */
    public SqlBuilder addSelectBean(String tableName, Class<?> clazz) {
        BeanSqlMeta meta = BeanSqlMetas.getMeta(clazz);
        meta.addSelectSql(quoteString, this, tableName);
        return this;
    }

    /**
     * <pre>
     * 将传入bean中的非空基本型字段按照XX=aa, YY=bb这样的方式添加到sql中去
     *
     * null值的字段默认不参与sql生成，如果想让某个字段在null时也参与生成sql
     * 请在该字段加注解@{@link DbField}并设置@{@link DbField#writeNullToDb()}=true
     * </pre>
     */
    public SqlBuilder addEqualsBean(Object bean) {
        _checkNull(bean);
        BeanSqlMeta meta = BeanSqlMetas.getMeta(bean.getClass());
        meta.addEqualsBeanSql(quoteString, this, bean);
        return this;
    }

    /**
     * <pre>
     * 将传入bean中的非空基本型字段按照(XX, YY, ZZ) values (aa, bb, cc)的方式添加到sql中去
     *
     * null值的字段默认不参与sql生成，如果想让某个字段在null时也参与生成sql
     * 请在该字段加注解@{@link DbField}并设置@{@link DbField#writeNullToDb()}=true
     * </pre>
     */
    public SqlBuilder addInsertBean(Object bean) {
        _checkNull(bean);
        BeanSqlMeta meta = BeanSqlMetas.getMeta(bean.getClass());
        meta.addInsertBeanSql(quoteString, this, bean);
        return this;
    }

    /**
     * 禁止null传入，否则sql生成肯定不正确，到了jdbc层抛出的异常很难看出问题来
     */
    private void _checkNull(Map<String, ?> map) {
        if (null == map) {
            throw new NullPointerException();
        } else if (map.isEmpty()) {
            throw new IllegalArgumentException("empty map");
        }
    }

    /**
     * 将传入Map<String,Object>中的非空基本型字段按照XX=aa, YY=bb这样的方式添加到sql中去
     */
    public SqlBuilder addEqualsBean(Map<String, ?> map) {
        _checkNull(map);
        BeanSqlMeta.addEqualsBeanSql(quoteString, this, map);
        return this;
    }

    /**
     * 将传入Map<String,Object>中的非空基本型字段按照(XX, YY, ZZ) values (aa, bb, cc)的方式添加到sql中去
     */
    public SqlBuilder addInsertBean(Map<String, ?> map) {
        _checkNull(map);
        BeanSqlMeta.addInsertBeanSql(quoteString, this, map);
        return this;
    }

    /**
     * 用于删除sql最后的,等符号，防止生成的sql有语法错误
     */
    void removeTail() {
        while (sql.length() > 0) {
            int index = sql.length() - 1;
            char ch = sql.charAt(index);
            if (ch == ' ' || ch == ',') {
                sql.deleteCharAt(index);
            } else {
                break;
            }
        }
    }

    @Override
    public String toString() {
        return "SqlBuilder [sql=" + sql + " args=" + args + "]";
    }
}
